
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;

using Swaf;
using Swaf.BizObj;
using Swaf.BizRule;
using Swaf.Container;
using Swaf.Security;

namespace Articles
{
	public class ArticlesBizRule : Syrinx.BaseBizRules
	{
		
		public IBizObjList getBlogTypes(IApplication app, IRuleContext context)
		{
			return app.appData["ArticleMetaTypeBlogTypes"] as IBizObjList;
		}
		public IBizObjList getArticleTypes(IApplication app,IRuleContext context) 
		{
			return app.appData["ArticleMetaTypeArticleTypes"] as IBizObjList;
		}
			 
		public string postExtendsGetLimitsDisplayTemplate(IApplication app, IRuleContext context, object returnValue, object[] inputParams)
		{
			if (returnValue != null)
			{
				string template = returnValue as string;
				if (CurrentCompanyId == ActiveCompanyId
					&& CurrentCompanyId != app.appData.get("userLoginOptions.rootCompanyId") as Swaf.BGuid.Guid)
					return template+=",Articles.Gui.AspNet.LimitsRODisplay";
				else
					return template+=",Articles.Gui.AspNet.LimitsRWDisplay";
			}

			return "Articles.Gui.AspNet.LimitsRODisplay";
		}
		
		public IBizObjList getStandAloneArticles(IApplication app, IRuleContext context, IBizObj criteria)
		{
			try
			{
				FlexiMap m = new FlexiMap();
				m["companyId"] = ActiveCompanyId;
				m["criteria"] = criteria;
				IBizObjList articles = app.appData.get("DA.Articles.GetStandaloneArticles", m) as IBizObjList;
				return articles;
			}
			catch (Exception e)
			{
				throw new ArticlesException("error executing getStandAloneArticles bizrule.", e);
			}
		}

		public Swaf.BGuid.Guid getArticleIdFromUniqueTitle(IApplication app, IRuleContext context, string uniqueTitle)
		{
            FlexiMap m = app.appData.get("cachedUniqueTitles") as FlexiMap;
            if (m == null)
                lock(GetType())
                    if((m = app.appData.get("cachedUniqueTitles") as FlexiMap) == null)
                        app.appData.put("cachedUniqueTitles", m = new FlexiMap());

            FlexiMap titleMap = m.get(ActiveCompanyId.ToString()) as FlexiMap;
            if (titleMap == null)
                lock(GetType())
                    if((titleMap = m.get(ActiveCompanyId.ToString()) as FlexiMap) == null)
                        m.put(ActiveCompanyId.ToString(), titleMap = new FlexiMap());

            Swaf.BGuid.Guid artId = titleMap.get(uniqueTitle) as Swaf.BGuid.Guid;
            if (artId == null)
            {
                context.put("uniqueTitle", uniqueTitle);
                context.put("companyId", ActiveCompanyId);
                context.put("listDetailLevel", DetailLevel.idOnly);
                IBizObj parent = app.appData.get("DA.Articles.GetArticleIdByUniqueTitle", context) as IBizObj;
                if (parent != null)
                    titleMap.put(uniqueTitle, artId = parent.get("id") as Swaf.BGuid.Guid);
            }
            return artId;
		}

		public IBizObjList getArticleParents(IApplication app, IRuleContext context, string treeName, Swaf.BGuid.Guid id, string dir, bool stripLeading)
		{
			context.put("id", id);
			context.put("dir", dir);
			context.put("stripLeading", stripLeading);
			IBizObjList parents = (IBizObjList)app.appData.get("DA.Articles.GetArticleParentPath", context);

			return parents;
		}


		public IBizObjList getArticles(IApplication app, IRuleContext context, INameAddressSupport info, IBizObj criteria)
		{
			string bizObjType = info.get("BizObjType") != null ? info.get("BizObjType").ToString() : "Article";
			int pageSize = 50;
			if (info.containsField("pageSize"))
				Int32.TryParse(info.get("pageSize").ToString(), out pageSize);

			//IBizObjList arts = new Swaf.BizObj.Virtual.SimpleVirtualBizObjList(bizObjType,"getChunkOfArticles", "getArticleCount", pageSize, new object[] { info, criteria });
			//int i = arts.numElements;//Will get the number of elements in list before returning..

			try
			{
				//Ensure that we have the parent id field.  If they provided the article uniqueTitle,
				//we have to query for the id first.

				Swaf.BGuid.Guid companyId = info.get("companyId") as Swaf.BGuid.Guid;
				Swaf.BGuid.Guid parentId = info.get("parentId") as Swaf.BGuid.Guid;
				string parentName = info.get("parentId") as string;
				if(parentId == null)
					parentId = Swaf.BGuid.Guid.TryParse(parentName);
				if (parentId != null)
					info.put("parentId", parentId);
				else if (!Cmn.IsEmpty(parentName))
				{
                    info.put("parentId", parentId = getArticleIdFromUniqueTitle(app, context, parentName));
                    if(parentId == null)
                        return app.bizObjMgr.createList("Article");
                    //context.put("uniqueTitle", parentName);
                    //context.put("companyId", companyId==null ? ActiveCompanyId : companyId);
                    //context.put("listDetailLevel", DetailLevel.idOnly);
                    //IBizObj obj = app.appData.get("DA.Articles.GetArticleIdByUniqueTitle", context) as IBizObj;
                    //if(obj == null)
                    //    return app.bizObjMgr.createList("Article");

                    //info.put("parentId", parentId = obj.get("id") as Swaf.BGuid.Guid);
				}

				//Before getting list, ensure that current user has rights to view this list.
				ISecurityPrincipal currentPrinc = CurrentPrincipal;
                //if (parentId != null)
                //{
                //    context.put("id", parentId);
                //    context.put("dir", "DESC");
                //    IBizObjList parents = app.appData.get("DA.Syrinx.GetTreeParentPath", context) as IBizObjList;
                //    for(int pos = 0; pos < parents.numElements;pos++)
                //    {
                //        IBizObj parent = parents[pos];
                //        Privileges priv = currentPrinc.getPrivilege("AppFunctions." + parent.get("id"), pos == parents.numElements-1);
                //        if (priv == Privileges.None)
                //            return null;
                //        else if(priv != Privileges.NotSet)
                //            break;
                //    }
                //}

				info.put("includeTranslations", info.get("includeTranslations") == null ? !app.resMgr.UsingBuiltInCulture : (bool)info.get("includeTranslations"));
				info.put("companyId", companyId == null ? ActiveCompanyId : companyId);
				info.put("criteria", criteria);
				info.put("defaultCacheName", info.get("defaultCacheName") == null ? ActiveCompany.get("settings.defaultMediaCache") : info.get("defaultCacheName"));
				if (info.get("objectTreeName") == null)
					info.put("objectTreeName", "Articles");
				if (info.get("startDepth") is String)
					info.put("startDepth", int.Parse(info.get("startDepth").ToString()));
				if (info.get("endDepth") is String)
					info.put("endDepth", int.Parse(info.get("endDepth").ToString()));
				if (info.get("listDetailLevel") is string)
					info.put("listDetailLevel", (DetailLevel)Enum.Parse(typeof(DetailLevel), (string)info.get("listDetailLevel")));

				IBizObjList arts = null;
				//We need to change the active company id to the company that the articles are for so that
				//any reference to the activeCompanyId in the dar will point to the correct company.
				//Swaf.BGuid.Guid oldCmpId = ActiveCompanyId;
				//ActiveCompanyId = companyId;
				try
				{
					arts = app.appData.get("DA.Articles.GetArticles_basic", info) as IBizObjList;
				}
				finally
				{
				//	ActiveCompanyId = oldCmpId;
				}


				//Check Security for each article and parenting
				Dictionary<Swaf.BGuid.Guid, bool> noSecurity = new Dictionary<Swaf.BGuid.Guid, bool>();
				for (int pos = 0; pos < arts.numElements; pos++)
				{
					IBizObj obj = arts[pos];
					Privileges artPriv = currentPrinc.getPrivilege("AppFunctions." + obj.get("objectId"));
                    Privileges rootPriv = currentPrinc.getPrivilege("AppFunctions." + obj.get("rootId"));
                    if (noSecurity.ContainsKey(obj.get("parentId") as Swaf.BGuid.Guid) ||
                        artPriv < Privileges.ReadOnly ||
                        rootPriv < Privileges.ReadOnly)
                    {
                        arts.RemoveAt(pos--);
                        noSecurity[obj.get("id") as Swaf.BGuid.Guid] = true;
                    }
                    else
                    {
                    }
					if(obj.get("id").Equals(obj.get("parentId")))
					{
						obj.put("parentId", app.appData.get("ArticleParentDropDown-Root.id"));
						obj.clearDirtyFlags();
					}
				}
				return arts;

			}
			catch (Exception e)
			{
				throw new ArticlesException("error executing getArticles.", e);
			}

		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="app"></param>
		/// <param name="context"></param>
		/// <param name="id">article group id</param>
		/// <returns></returns>
		public IBizObjList getChunkOfArticles(IApplication app, IRuleContext context, int pageSize, int currentPage, INameAddressSupport info,IBizObj criteria)
		{
			try
			{
				//Ensure that we have the parent id field.  If they provided the article uniqueTitle,
				//we have to query for the id first.

				Swaf.BGuid.Guid companyId = info.get("companyId") as Swaf.BGuid.Guid;
				Swaf.BGuid.Guid parentId = info.get("parentId") as Swaf.BGuid.Guid;
				string parentName = info.get("parentId") as string;
				if(parentId == null)
					parentId = Swaf.BGuid.Guid.TryParse(parentName);
				if (parentId != null)
					info.put("parentId", parentId);
				else if (parentName != null && parentName.Length != 0)
				{
					context.put("uniqueTitle", parentName);
					context.put("companyId", companyId==null ? ActiveCompanyId : companyId);
					context.put("listDetailLevel", DetailLevel.idOnly);
					IBizObj obj = app.appData.get("DA.Articles.GetArticleIdByUniqueTitle", context) as IBizObj;
					if(obj == null)
						return app.bizObjMgr.createList("Article");

					info.put("parentId", parentId = obj.get("id") as Swaf.BGuid.Guid);
				}

				//Before getting list, ensure that current user has rights to view this list.
				ISecurityPrincipal currentPrinc = CurrentPrincipal;
				if (parentId != null)
				{
					context.put("id", parentId);
					context.put("dir", "DESC");
					IBizObjList parents = app.appData.get("DA.Syrinx.GetTreeParentPath", context) as IBizObjList;
					for(int pos = 0; pos < parents.numElements;pos++)
					{
						IBizObj parent = parents[pos];
						Privileges priv = currentPrinc.getPrivilege("AppFunctions." + parent.get("id"), pos == parents.numElements-1);
						if (priv == Privileges.None)
							return null;
						else if(priv != Privileges.NotSet)
							break;
					}
				}

				info.put("includeTranslations", info.get("includeTranslations") == null ? !app.resMgr.UsingBuiltInCulture : (bool)info.get("includeTranslations"));
				info.put("companyId", companyId == null ? ActiveCompanyId : companyId);
				info.put("criteria", criteria);
				info.put("defaultCacheName", info.get("defaultCacheName") == null ? ActiveCompany.get("settings.defaultMediaCache") : info.get("defaultCacheName"));
				if (info.get("objectTreeName") == null)
					info.put("objectTreeName", "Articles");
				if (info.get("startDepth") is String)
					info.put("startDepth", int.Parse(info.get("startDepth").ToString()));
				if (info.get("endDepth") is String)
					info.put("endDepth", int.Parse(info.get("endDepth").ToString()));
				if (info.get("listDetailLevel") is string)
					info.put("listDetailLevel", (DetailLevel)Enum.Parse(typeof(DetailLevel), (string)info.get("listDetailLevel")));
				info.put("pageSize", pageSize);
				info.put("currentPage", currentPage);

				IBizObjList list = null;
				//We need to change the active company id to the company that the articles are for so that
				//any reference to the activeCompanyId in the dar will point to the correct company.
				Swaf.BGuid.Guid oldCmpId = ActiveCompanyId;
				ActiveCompanyId = companyId;
				try
				{
					list = app.appData.get("DA.Articles.GetArticles", info) as IBizObjList;
				}
				finally
				{
					ActiveCompanyId = oldCmpId;
				}


				//Check Security for each article and parenting
				Dictionary<Swaf.BGuid.Guid, bool> noSecurity = new Dictionary<Swaf.BGuid.Guid, bool>();
				for(int pos = 0; pos < list.numElements; pos++)
				{
					IBizObj obj = list[pos];
					object privObj = currentPrinc.getPrivilege("AppFunctions." + obj.get("objectId"));
					if (noSecurity.ContainsKey(obj.get("parentId") as Swaf.BGuid.Guid) ||
						privObj is Privileges &&(Privileges)privObj < Privileges.ReadOnly)
					{
						list.RemoveAt(pos--);
						noSecurity[obj.get("id") as Swaf.BGuid.Guid] = true;
					}
					if(obj.get("id").Equals(obj.get("parentId")))
					{
						obj.put("parentId", app.appData.get("ArticleParentDropDown-Root.id"));
						obj.clearDirtyFlags();
					}
				}

				return list;
			}
			catch (Exception e)
			{
				throw new ArticlesException("error executing getArticles.", e);
			}
		}
		public int getArticleCount(IApplication app, IRuleContext context, INameAddressSupport info, IBizObj criteria)
		{
			try
			{
				//Ensure that we have the parent id field.  If they provided the article uniqueTitle,
				//we have to query for the id first.
				Swaf.BGuid.Guid companyId = info.get("companyId") as Swaf.BGuid.Guid;

				Swaf.BGuid.Guid parentId = info.get("parentId") as Swaf.BGuid.Guid;
				string parentName = info.get("parentId") as string;
				if (parentId == null)
					parentId = Swaf.BGuid.Guid.TryParse(parentName);
				if (parentId != null)
					info.put("parentId", parentId);
				else if (parentName != null && parentName.Length != 0)
				{
					context.put("uniqueTitle", parentName);
					context.put("companyId",companyId==null? ActiveCompanyId:companyId);
					context.put("listDetailLevel", DetailLevel.idOnly);
					IBizObj obj = app.appData.get("DA.Articles.GetArticleIdByUniqueTitle", context) as IBizObj;
					if (obj == null)
						return 0;

					info.put("parentId", parentId = obj.get("id") as Swaf.BGuid.Guid);
				}

				//Before getting list, ensure that current user has rights to view this list.
				ISecurityPrincipal currentPrinc = CurrentPrincipal;
				if (parentId != null)
				{
					context.put("id", parentId);
					context.put("dir", "DESC");
					IBizObjList parents = app.appData.get("DA.Syrinx.GetTreeParentPath", context) as IBizObjList;
					for (int pos = 0; pos < parents.numElements; pos++)
					{
						IBizObj parent = parents[pos];
						Privileges priv = currentPrinc.getPrivilege("AppFunctions." + parent.get("id"), pos == parents.numElements - 1);
						if (priv == Privileges.None)
							return 0;
						else if (priv != Privileges.NotSet)
							break;
					}
				}

				info.put("companyId",companyId==null? ActiveCompanyId:companyId);
				info.put("criteria", criteria);
				if (info.get("objectTreeName") == null)
					info.put("objectTreeName", "Articles");
				if (info.get("startDepth") is String)
					info.put("startDepth", int.Parse(info.get("startDepth").ToString()));
				if (info.get("endDepth") is String)
					info.put("endDepth", int.Parse(info.get("endDepth").ToString()));
				IBizObj count = app.appData.get("DA.Articles.GetArticleCount", info) as IBizObj;
				return (count != null)?(int)count.get("count"):0;
			}
			catch (Exception e)
			{
				throw new ArticlesException("error executing getArticles.", e);
			}
		}

		public IBizObjList getArticlesByRootId(IApplication app, IRuleContext context, Swaf.BGuid.Guid rootId,int from,int to)
		{
			context.put("rootId", rootId);
			context.put("depthFrom", from);
			context.put("depthTo", to);
			IBizObjList l = app.appData.get("DA.Articles.GetArticlesByRootId", context) as IBizObjList;
			return l;
		}

		public IBizObjList getAllCompanyArticles(IApplication app, IRuleContext context)
		{
			context.put("companyId", ActiveCompanyId);
			IBizObjList list = app.appData.get("DA.Articles.GetAllCompanyArticleIdTitle", context) as IBizObjList;
			return list;
		}

		public IBizObjList getYellowPagesCatagories(IApplication app, IRuleContext context)
		{
			IBizObjList list = app.appData.get("DA.Articles.GetYellowPagesCatagories", context) as IBizObjList;
			return list;
		}

		public IBizObjList getAllCompanyArticlesForParent(IApplication app, IRuleContext context)
		{
			context.put("companyId", ActiveCompanyId);
			context.put("treeName", "Articles");
			IBizObjList list = app.appData.get("DA.Articles.GetAllCompanyArticleIdTitle", context) as IBizObjList;
			list.Insert(0,(IBizObj)app.appData.get("ArticleParentDropDown-Root"));
			return list;
		}

		public IBizObj getArticleDetail(IApplication app, IRuleContext context,object articleId,bool withTranslation,DetailLevel detailLevel, bool incrementCount, bool stripLeading)
		{
			if (articleId == null)
				return null;

			try
			{
                Swaf.BGuid.Guid cmpId = ActiveCompanyId;
                Swaf.BGuid.Guid gid = articleId as Swaf.BGuid.Guid;
				if(gid == null)
					gid = Swaf.BGuid.Guid.TryParse(articleId.ToString());
				if (gid != null)
				{
					context.put("articleId", gid);
					articleId = null;
				}
				context.put("uniqueTitle", articleId as string);
				context.put("includeTranslations", withTranslation || !app.resMgr.UsingBuiltInCulture);
				context.put("includeBody", true);
                context.put("stripLeading", stripLeading);
				context.put("companyId", ActiveCompanyId);
				context.put("details", detailLevel);
				IBizObj detail = app.appData.get("DA.Articles.GetArticleDetails",context) as IBizObj;
                if (detail != null)
                {
                    if (detail.get("mediaCacheName").ToString().Length == 0)
                        detail.put("mediaCacheName", ActiveCompany.get("settings.defaultMediaCache"));
                    if (detail.get("id").Equals(detail.get("parentId")))
                        detail.put("parentId", app.appData.get("ArticleParentDropDown-Root.id"));

                    detail.clearDirtyFlags();

                    //Do an async call to increate parent article view counts to avoid waiting for this work to return
                    //article detail to client.
                    //if (detail != null && incrementCount)
                    //	context.RuleGroup.execute(context, "increateNodeViewCount", null, "", new object[] { detail.get("id") });
                    context.RuleGroup.execute("increateNodeViewCount", detail.get("id"));
                }

				return detail;
				
			}
			catch (Exception e)
			{
				throw new ArticlesException("error executing getArticleDetail bizrule.", e);
			}
		}

        public IBizObj getArticleTemplate(IApplication app, IRuleContext context, object articleId, bool withTranslation, DetailLevel detailLevel, bool incrementCount)
        {
            try
            {
                string sFilePath = "", sFileBuffer = null;
				string imageDir = app.resMgr.getMacro("ArticleWidgetTemplateImagesLocation");
				if (imageDir == null)
					imageDir = @"/images/ArticleWidgetTemplateImages";
                string uniqueDir = app.resMgr.getMacro("ArticleWidgetTemplateLocation");
				if (uniqueDir == null)
					uniqueDir = @"..\ArticleWidgetTemplates";

                IBizObj art = app.ruleMgr.execute("getArticleDetail", articleId, withTranslation, detailLevel, incrementCount, true).CallResults as IBizObj;
				if (art != null)
                {
                    // the actual regex Pattern to find
					string sReg = "<img.*src=\"?'?" + imageDir + "/.*[0-9].[jpg|gif|png]\"?'?.*>";
                    StringBuilder baseBody = new StringBuilder((string)art.get("baseBody"));
                    MatchCollection c = Regex.Matches(baseBody.ToString(), sReg, RegexOptions.IgnoreCase | RegexOptions.Multiline);

                    if (c.Count > 0)
                    {
                        FlexiMap m = new FlexiMap();
                        string sRegFileName = "/.*[0-9]{1,4}", idVal = "";
                        Match name, num = null, sid = null;
                        string[] sx ={ "id=\"", "\"","id=", " " };
                        foreach (Match match in c)
                        {
                            name = Regex.Match(match.Value, sRegFileName);
                            sid = Regex.Match(match.Value, "id=\"?.*?\"?\\s");
                            if (sid != null && sid.Value.Length > 0)
                                idVal = sid.Value.Split(sx, StringSplitOptions.RemoveEmptyEntries)[0];
                            num = Regex.Match(name.Value, "[0-9]{1,4}");
							string realName = name.Value.Remove(num.Index);
							realName = realName.Substring(realName.LastIndexOf('/')+1);
                            sFilePath = string.Format("{0}.ascx", realName);
                            sFilePath = System.IO.Path.Combine(uniqueDir, sFilePath);
                            if (System.IO.File.Exists(sFilePath))
                            {
                                sFileBuffer = System.IO.File.ReadAllText(sFilePath);
                                if (!m.Contains(match.Value))
                                {
                                    baseBody = baseBody.Replace(match.Value, sFileBuffer);
                                    m[match.Value] = true;
                                }
                            }
                        }
                        m["id"] = idVal;
                        if (num != null)
                            m["bizProcSet"] = num.Value;
                        art.put("baseBody", baseBody.ToString());//app.resMgr.resolve(baseBody.ToString(), m));
                        sFileBuffer = null; baseBody = null;// clean up now
                    }
                }
                return art;
            }
            catch (Exception e)
            {
                throw new ArticlesException("error executing getArticleTemplates", e);
            }
        }

		/// <summary>
		/// This bizrule is called by the UserLogin feature when it needs to get the list of avilable app
		/// functions for users of the application.
		/// This bizrule returns a CSF security AppFunction bizobjlist that is derived from the current user's
		/// active company's article groups.  Each article group in the application is used to define a corrisponding
		/// Security privilege which is exposed as an AppFunction.
		/// </summary>
		/// <remarks>All of the articles groups for a given company become a part of a single group of
		/// application functions.  When an administrator is configuring the security access rights to various
		/// user groups each article group will be shown in the application function list and the administrator
		/// will be able to specify the access privilege for the article group.  If a user is given a privilege
		/// of None for a given article group, they will not be able to see it.  If a user is given a privilege
		/// of Delete, they will be able to delete the article group, add new article groups and add article set in the
		/// article group.</remarks>
		/// <param name="specifierId">The article group AppFunction specifier id.</param>
		/// <returns></returns>
		public IBizObjList getArticleAppFunctions(IApplication app, IRuleContext context, string specifierId)
		{
			try
			{
				if (specifierId == null)
					specifierId = "ArticleAppFunctions";
				context.put("specifierId", specifierId);
				context.put("companyId", ActiveCompanyId);
				return (IBizObjList)app.appData.get("DA.Articles.GetAppFunctions", context);
			}
			catch (Exception e)
			{
				throw new ArticlesException("Error executing getArticleGroupAppFunctions bizRule.", e);
			}
		}

		protected static string[] s_articleFieldsToReplace = new string[]{"title", "summary", "body"};
		public IBizObjList getArticlesFromUserMessages(IApplication app, IRuleContext context, List<Swaf.Gui.UserMessage> msgs)
		{
			Dictionary<string, IBizObj> missing = null;
			IRuleGroup grp = null;
			IBizObjList articles = app.bizObjMgr.createList("Article");
			if (msgs != null && msgs.Count != 0)
			{
				foreach (Swaf.Gui.UserMessage msg in msgs)
				{
					if (grp == null)
						grp = app.ruleMgr.execute("getArticleDetail", null, "", new object[] { msg.ArticleId, app.resMgr.UsingBuiltInCulture, DetailLevel.basicDetails, true, true });
					else
						grp.execute("getArticleDetail", null, "", new object[] { msg.ArticleId, app.resMgr.UsingBuiltInCulture, DetailLevel.basicDetails, true, true });
				}
				Debug.Assert(grp != null);
				grp.waitForCompletion(true);
				for (int p = 0; p < msgs.Count; p++)
				{
					IBizObj article = grp.getCallResults(p) as IBizObj;
					if (article == null)
					{
						if (missing == null)
							missing = new Dictionary<string, IBizObj>();
						if (!missing.TryGetValue(msgs[p].ArticleId, out article))
							missing[msgs[p].ArticleId] = article = createSystemMessageArticleFromUniqueTitle(app, context, msgs[p].ArticleId);
					}

					if (article != null)
						foreach (string fld in s_articleFieldsToReplace)
							article.put(fld, app.resMgr.resolve(article.get(fld).ToString(), new NameAddressSupportDictionaryWrapper(msgs[p].ReplacementMacros)));

					//bool addArticle = true;
					//for(int p2 = 0; p2 < articles.numElements; p2++)
					//    if (((string)article.get("baseTitle")).CompareTo(articles[p2].get("baseTitle")) == 0 ||
					//        ((string)article.get("baseSummary")).CompareTo(articles[p2].get("baseSummary")) == 0)
					//    {
					//        addArticle = false;
					//        break;
					//    }
					//if(addArticle)
						articles.Add(article);
				}
			}
			return articles;
		}

		protected IBizObj createSystemMessageArticleFromUniqueTitle(IApplication app, IRuleContext context, string uniqueTitle)
		{
			IBizObj obj = App.bizObjMgr.create("Article");
			Swaf.BGuid.Guid pid = null;
			string parentTitle = "SystemMessages";
			if (uniqueTitle.StartsWith("Error", StringComparison.OrdinalIgnoreCase))
				parentTitle = "SystemErrorMessages";
			else if (uniqueTitle.StartsWith("Warning", StringComparison.OrdinalIgnoreCase))
				parentTitle = "SystemWarningMessages";
			else if (uniqueTitle.StartsWith("Info", StringComparison.OrdinalIgnoreCase))
				parentTitle = "SystemInfoMessages";

			IResults rc = app.ruleMgr.execute("getArticleIdFromUniqueTitle", parentTitle);
			pid = rc.CallResults as Swaf.BGuid.Guid;

			obj.put("parentId", pid);
			obj.put("isSystem", true);
			obj.put("id", new Swaf.BGuid.Guid());
			obj.put("baseTitle", string.Format("!!!{0}", uniqueTitle));
			obj.put("uniqueTitle", uniqueTitle);
			obj.put("baseSummary", string.Format("System message {0} not found. New one created. Please edit.", uniqueTitle));
			obj.put("treeName", "Articles");
			rc = app.ruleMgr.execute("updateArticle", obj);

			return obj;
		}

	}

	public enum ArticleGroupViewOption
	{
		All,
		SiteOnly,
		UserOnly,
		SiteAndUser
	}
}
